/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"nsImageClipboard.h"#include"gfxUtils.h"#include"mozilla/gfx/2D.h"#include"mozilla/gfx/DataSurfaceHelpers.h"#include"mozilla/RefPtr.h"#include"nsITransferable.h"#include"nsGfxCIID.h"#include"nsMemory.h"#include"prmem.h"#include"imgIEncoder.h"#include"nsLiteralString.h"#include"nsComponentManagerUtils.h"#define BFH_LENGTH 14usingnamespacemozilla;usingnamespacemozilla::gfx;/* Things To Do 11/8/00Check image metrics, can we support them? Do we need to?Any other render format? HTML?*///// nsImageToClipboard ctor//// Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard//nsImageToClipboard::nsImageToClipboard(imgIContainer*aInImage,boolaWantDIBV5):mImage(aInImage),mWantDIBV5(aWantDIBV5){// nothing to do here}//// nsImageToClipboard dtor//// Clean up after ourselves. We know that we have created the bitmap// successfully if we still have a pointer to the header.//nsImageToClipboard::~nsImageToClipboard(){}//// GetPicture//// Call to get the actual bits that go on the clipboard. If an error // ocurred during conversion, |outBits| will be null.//// NOTE: The caller owns the handle and must delete it with ::GlobalRelease()//nsresultnsImageToClipboard::GetPicture(HANDLE*outBits){NS_ASSERTION(outBits,"Bad parameter");returnCreateFromImage(mImage,outBits);}// GetPicture//// CalcSize//// Computes # of bytes needed by a bitmap with the specified attributes.//int32_tnsImageToClipboard::CalcSize(int32_taHeight,int32_taColors,WORDaBitsPerPixel,int32_taSpanBytes){int32_tHeaderMem=sizeof(BITMAPINFOHEADER);// add size of pallette to header sizeif(aBitsPerPixel<16)HeaderMem+=aColors*sizeof(RGBQUAD);if(aHeight<0)aHeight=-aHeight;return(HeaderMem+(aHeight*aSpanBytes));}//// CalcSpanLength//// Computes the span bytes for determining the overall size of the image//int32_tnsImageToClipboard::CalcSpanLength(uint32_taWidth,uint32_taBitCount){int32_tspanBytes=(aWidth*aBitCount)>>5;if((aWidth*aBitCount)&0x1F)spanBytes++;spanBytes<<=2;returnspanBytes;}//// CreateFromImage//// Do the work to setup the bitmap header and copy the bits out of the// image. //nsresultnsImageToClipboard::CreateFromImage(imgIContainer*inImage,HANDLE*outBitmap){nsresultrv;*outBitmap=nullptr;RefPtr<SourceSurface>surface=inImage->GetFrame(imgIContainer::FRAME_CURRENT,imgIContainer::FLAG_SYNC_DECODE);NS_ENSURE_TRUE(surface,NS_ERROR_FAILURE);MOZ_ASSERT(surface->GetFormat()==SurfaceFormat::B8G8R8A8||surface->GetFormat()==SurfaceFormat::B8G8R8X8);RefPtr<DataSourceSurface>dataSurface;if(surface->GetFormat()==SurfaceFormat::B8G8R8A8){dataSurface=surface->GetDataSurface();}else{// XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8// directly below once bug 995807 is fixed.dataSurface=gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(surface,SurfaceFormat::B8G8R8A8);}NS_ENSURE_TRUE(dataSurface,NS_ERROR_FAILURE);nsCOMPtr<imgIEncoder>encoder=do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp",&rv);NS_ENSURE_SUCCESS(rv,rv);uint32_tformat;nsAutoStringoptions;if(mWantDIBV5){options.AppendLiteral("version=5;bpp=");}else{options.AppendLiteral("version=3;bpp=");}switch(dataSurface->GetFormat()){caseSurfaceFormat::B8G8R8A8:format=imgIEncoder::INPUT_FORMAT_HOSTARGB;options.AppendInt(32);break;#if 0 // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed. case SurfaceFormat::B8G8R8X8: format = imgIEncoder::INPUT_FORMAT_RGB; options.AppendInt(24); break;#endifdefault:NS_NOTREACHED("Unexpected surface format");returnNS_ERROR_INVALID_ARG;}DataSourceSurface::MappedSurfacemap;boolmappedOK=dataSurface->Map(DataSourceSurface::MapType::READ,&map);NS_ENSURE_TRUE(mappedOK,NS_ERROR_FAILURE);rv=encoder->InitFromData(map.mData,0,dataSurface->GetSize().width,dataSurface->GetSize().height,map.mStride,format,options);dataSurface->Unmap();NS_ENSURE_SUCCESS(rv,rv);uint32_tsize;encoder->GetImageBufferUsed(&size);NS_ENSURE_TRUE(size>BFH_LENGTH,NS_ERROR_FAILURE);HGLOBALglob=::GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE|GMEM_ZEROINIT,size-BFH_LENGTH);if(!glob)returnNS_ERROR_OUT_OF_MEMORY;char*dst=(char*)::GlobalLock(glob);char*src;rv=encoder->GetImageBuffer(&src);NS_ENSURE_SUCCESS(rv,rv);::CopyMemory(dst,src+BFH_LENGTH,size-BFH_LENGTH);::GlobalUnlock(glob);*outBitmap=(HANDLE)glob;returnNS_OK;}nsImageFromClipboard::nsImageFromClipboard(){// nothing to do here}nsImageFromClipboard::~nsImageFromClipboard(){}//// GetEncodedImageStream//// Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream//nsresultnsImageFromClipboard::GetEncodedImageStream(unsignedchar*aClipboardData,constchar*aMIMEFormat,nsIInputStream**aInputStream){NS_ENSURE_ARG_POINTER(aInputStream);NS_ENSURE_ARG_POINTER(aMIMEFormat);nsresultrv;*aInputStream=nullptr;// pull the size information out of the BITMAPINFO header and// initialize the imageBITMAPINFO*header=(BITMAPINFO*)aClipboardData;int32_twidth=header->bmiHeader.biWidth;int32_theight=header->bmiHeader.biHeight;// neg. heights mean the Y axis is inverted and we don't handle that caseNS_ENSURE_TRUE(height>0,NS_ERROR_FAILURE);unsignedchar*rgbData=newunsignedchar[width*height*3/* RGB */];if(rgbData){BYTE*pGlobal=(BYTE*)aClipboardData;// Convert the clipboard image into RGB packed pixel datarv=ConvertColorBitMap((unsignedchar*)(pGlobal+header->bmiHeader.biSize),header,rgbData);// if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbDataif(NS_SUCCEEDED(rv)){nsAutoCStringencoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type="));// Map image/jpg to image/jpeg (which is how the encoder is registered).if(strcmp(aMIMEFormat,kJPGImageMime)==0)encoderCID.AppendLiteral("image/jpeg");elseencoderCID.Append(aMIMEFormat);nsCOMPtr<imgIEncoder>encoder=do_CreateInstance(encoderCID.get(),&rv);if(NS_SUCCEEDED(rv)){rv=encoder->InitFromData(rgbData,0,width,height,3*width/* RGB * # pixels in a row */,imgIEncoder::INPUT_FORMAT_RGB,EmptyString());if(NS_SUCCEEDED(rv)){encoder.forget(aInputStream);}}}delete[]rgbData;}elserv=NS_ERROR_OUT_OF_MEMORY;returnrv;}// GetImage//// InvertRows//// Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place.//voidnsImageFromClipboard::InvertRows(unsignedchar*aInitialBuffer,uint32_taSizeOfBuffer,uint32_taNumBytesPerRow){if(!aNumBytesPerRow)return;uint32_tnumRows=aSizeOfBuffer/aNumBytesPerRow;unsignedchar*row=newunsignedchar[aNumBytesPerRow];uint32_tcurrentRow=0;uint32_tlastRow=(numRows-1)*aNumBytesPerRow;while(currentRow<lastRow){// store the current row into a temporary buffermemcpy(row,&aInitialBuffer[currentRow],aNumBytesPerRow);memcpy(&aInitialBuffer[currentRow],&aInitialBuffer[lastRow],aNumBytesPerRow);memcpy(&aInitialBuffer[lastRow],row,aNumBytesPerRow);lastRow-=aNumBytesPerRow;currentRow+=aNumBytesPerRow;}delete[]row;}//// ConvertColorBitMap//// Takes the clipboard bitmap and converts it into a RGB packed pixel values.//nsresultnsImageFromClipboard::ConvertColorBitMap(unsignedchar*aInputBuffer,PBITMAPINFOpBitMapInfo,unsignedchar*aOutBuffer){uint8_tbitCount=pBitMapInfo->bmiHeader.biBitCount;uint32_timageSize=pBitMapInfo->bmiHeader.biSizeImage;// may be zero for BI_RGB bitmaps which means we need to calculate by handuint32_tbytesPerPixel=bitCount/8;if(bitCount<=4)bytesPerPixel=1;// rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't // correspond to biWidth.uint32_trowSize=(bitCount*pBitMapInfo->bmiHeader.biWidth+7)/8;// +7 to round upif(rowSize%4)rowSize+=(4-(rowSize%4));// Pad to DWORD Boundary// if our buffer includes a color map, skip over it if(bitCount<=8){int32_tbytesToSkip=(pBitMapInfo->bmiHeader.biClrUsed?pBitMapInfo->bmiHeader.biClrUsed:(1<<bitCount))*sizeof(RGBQUAD);aInputBuffer+=bytesToSkip;}bitFieldscolorMasks;// only used if biCompression == BI_BITFIELDSif(pBitMapInfo->bmiHeader.biCompression==BI_BITFIELDS){// color table consists of 3 DWORDS containing the color masks...colorMasks.red=(*((uint32_t*)&(pBitMapInfo->bmiColors[0])));colorMasks.green=(*((uint32_t*)&(pBitMapInfo->bmiColors[1])));colorMasks.blue=(*((uint32_t*)&(pBitMapInfo->bmiColors[2])));CalcBitShift(&colorMasks);aInputBuffer+=3*sizeof(DWORD);}elseif(pBitMapInfo->bmiHeader.biCompression==BI_RGB&&!imageSize)// BI_RGB can have a size of zero which means we figure it out{// XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each rowimageSize=rowSize*pBitMapInfo->bmiHeader.biHeight;}// The windows clipboard image format inverts the rows InvertRows(aInputBuffer,imageSize,rowSize);if(!pBitMapInfo->bmiHeader.biCompression||pBitMapInfo->bmiHeader.biCompression==BI_BITFIELDS){uint32_tindex=0;uint32_twriteIndex=0;unsignedcharredValue,greenValue,blueValue;uint8_tcolorTableEntry=0;int8_tbit;// used for grayscale bitmaps where each bit is a pixeluint32_tnumPixelsLeftInRow=pBitMapInfo->bmiHeader.biWidth;// how many more pixels do we still need to read for the current rowuint32_tpos=0;while(index<imageSize){switch(bitCount){case1:for(bit=7;bit>=0&&numPixelsLeftInRow;bit--){colorTableEntry=(aInputBuffer[index]>>bit)&1;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbRed;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;numPixelsLeftInRow--;}pos+=1;break;case4:{// each aInputBuffer[index] entry contains data for two pixels.// read the first pixelcolorTableEntry=aInputBuffer[index]>>4;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbRed;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;numPixelsLeftInRow--;if(numPixelsLeftInRow)// now read the second pixel{colorTableEntry=aInputBuffer[index]&0xF;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbRed;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;numPixelsLeftInRow--;}pos+=1;}break;case8:aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen;aOutBuffer[writeIndex++]=pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue;numPixelsLeftInRow--;pos+=1;break;case16:{uint16_tnum=0;num=(uint8_t)aInputBuffer[index+1];num<<=8;num|=(uint8_t)aInputBuffer[index];redValue=((uint32_t)(((float)(num&0xf800)/0xf800)*0xFF0000)&0xFF0000)>>16;greenValue=((uint32_t)(((float)(num&0x07E0)/0x07E0)*0x00FF00)&0x00FF00)>>8;blueValue=((uint32_t)(((float)(num&0x001F)/0x001F)*0x0000FF)&0x0000FF);// now we have the right RGB values...aOutBuffer[writeIndex++]=redValue;aOutBuffer[writeIndex++]=greenValue;aOutBuffer[writeIndex++]=blueValue;numPixelsLeftInRow--;pos+=2;}break;case32:case24:if(pBitMapInfo->bmiHeader.biCompression==BI_BITFIELDS){uint32_tval=*((uint32_t*)(aInputBuffer+index));aOutBuffer[writeIndex++]=(val&colorMasks.red)>>colorMasks.redRightShift<<colorMasks.redLeftShift;aOutBuffer[writeIndex++]=(val&colorMasks.green)>>colorMasks.greenRightShift<<colorMasks.greenLeftShift;aOutBuffer[writeIndex++]=(val&colorMasks.blue)>>colorMasks.blueRightShift<<colorMasks.blueLeftShift;numPixelsLeftInRow--;pos+=4;// we read in 4 bytes of data in order to process this pixel}else{aOutBuffer[writeIndex++]=aInputBuffer[index+2];aOutBuffer[writeIndex++]=aInputBuffer[index+1];aOutBuffer[writeIndex++]=aInputBuffer[index];numPixelsLeftInRow--;pos+=bytesPerPixel;// 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)...}break;default:// This is probably the wrong place to check this...returnNS_ERROR_FAILURE;}index+=bytesPerPixel;// increment our loop counterif(!numPixelsLeftInRow){if(rowSize!=pos){// advance index to skip over remaining padding bytesindex+=(rowSize-pos);}numPixelsLeftInRow=pBitMapInfo->bmiHeader.biWidth;pos=0;}}// while we still have bytes to process}returnNS_OK;}voidnsImageFromClipboard::CalcBitmask(uint32_taMask,uint8_t&aBegin,uint8_t&aLength){// find the rightmost 1uint8_tpos;boolstarted=false;aBegin=aLength=0;for(pos=0;pos<=31;pos++){if(!started&&(aMask&(1<<pos))){aBegin=pos;started=true;}elseif(started&&!(aMask&(1<<pos))){aLength=pos-aBegin;break;}}}voidnsImageFromClipboard::CalcBitShift(bitFields*aColorMask){uint8_tbegin,length;// redCalcBitmask(aColorMask->red,begin,length);aColorMask->redRightShift=begin;aColorMask->redLeftShift=8-length;// greenCalcBitmask(aColorMask->green,begin,length);aColorMask->greenRightShift=begin;aColorMask->greenLeftShift=8-length;// blueCalcBitmask(aColorMask->blue,begin,length);aColorMask->blueRightShift=begin;aColorMask->blueLeftShift=8-length;}